using System;
using System.IO;
using System.Globalization;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Security.Permissions;
#if !PocketPC || DesignTime
using System.Runtime.Serialization;
using System.ComponentModel;
#endif
using System.Reflection;

namespace GeoFramework
{
	/// <summary>Represents a highly-precise two-dimensional size.</summary>
	/// <remarks>
	/// 	<para>This structure is a <em>GeoFrameworks</em> "parseable type" whose value can
	///     be freely converted to and from <strong>String</strong> objects via the
	///     <strong>ToString</strong> and <strong>Parse</strong> methods.</para>
	/// 	<para>Instances of this structure are guaranteed to be thread-safe because it is
	///     immutable (its properties can only be modified via constructors).</para>
	/// </remarks>
#if !PocketPC || DesignTime
#if Framework30
    [TypeConverter("GeoFramework.Design.SizeDConverter, GeoFramework.Design, Culture=neutral, Version=1.0.0.0, PublicKeyToken=3ed3cdf4fdda3400")]
#elif Framework20
    [TypeConverter("GeoFramework.Design.SizeDConverter, GeoFramework.Design, Culture=neutral, Version=1.0.0.0, PublicKeyToken=3ed3cdf4fdda3400")]
#else
	[TypeConverter(typeof(Design.SizeDConverter))]
#endif   
	[Serializable()]
    [ImmutableObject(true)]
#endif
#if Framework20
    [XmlSchemaProvider("GetXmlSchema")]
#endif
    public struct SizeD : IFormattable
#if !PocketPC || (PocketPC && Framework20)
	, IXmlSerializable
#endif  
#if !PocketPC || DesignTime
, ISerializable
#endif
    {
        private double pWidth;
		private double pHeight;

		/// <summary>Represents a size with no value.</summary>
		public static readonly SizeD Empty = new SizeD(0.0, 0.0);
		/// <summary>Represents an infinite size.</summary>
		public static readonly SizeD Infinity = new SizeD(Double.PositiveInfinity, Double.PositiveInfinity);
		/// <summary>Represents the smallest possible size.</summary>
		public static readonly SizeD Minimum = new SizeD(Double.MinValue, Double.MinValue);
		/// <summary>Represents the largest possible size.</summary>
		public static readonly SizeD Maximum = new SizeD(Double.MaxValue, Double.MaxValue);

		public SizeD(PointD pt)
		{
			pWidth = pt.X;
			pHeight = pt.Y;
		}

		public SizeD(SizeD size)
		{
			pWidth = size.Width;
			pHeight = size.Height;
		}

		/// <summary>Creates a new instance.</summary>
		public SizeD(double width, double height)
		{
			pWidth = width;
			pHeight = height;
		}

        public SizeD(string value)
            : this(value, CultureInfo.CurrentCulture)
        {}

        public SizeD(string value, CultureInfo culture)
        {
            // Split out the values
            string[] Values = value.Trim().Split(culture.TextInfo.ListSeparator.ToCharArray());
            
            // There should be two values
            if (Values.Length != 2)
                throw new FormatException(Resources.SizeD_InvalidFormat); 

            // PArse it out
            pWidth = double.Parse(Values[0].Trim(), culture);
            pHeight = double.Parse(Values[1].Trim(), culture);
        }

#if !PocketPC || DesignTime
        /// <summary>
        /// Creates a new instance by de-serializing previously-serialized information.
        /// </summary>
        /// <param name="info">A <strong>SerializationInfo</strong> object containing serialized data.</param>
        /// <param name="context">A <strong>StreamingContext</strong> object describing the context of serialization.</param>
        /// <remarks>This constructor will de-serialize a previously-serialized object.</remarks>
        private SizeD(SerializationInfo info, StreamingContext context)
        {
            pWidth = info.GetDouble("Width");
            pHeight = info.GetDouble("Height");
        }
#endif



		/// <summary>Returns the ratio width to height.</summary>
		public double AspectRatio
		{
			get
			{
				return pWidth / pHeight;
			}
		}

		public SizeD ToAspectRatio(SizeD size)
		{
			// Calculate the aspect ratio
			return ToAspectRatio((double)size.Width / (double)size.Height);
		}

		public SizeD ToAspectRatio(double aspectRatio)
		{
			double CurrentAspect = AspectRatio;
			// Do the values already match?
			if(CurrentAspect == aspectRatio) return this;
			// Is the new ratio higher or lower?
			if(aspectRatio > CurrentAspect)
			{
				// Inflate the GeographicRectangle to the new height minus the current height
				// TESTS OK
				return new SizeD(pWidth + 
					(aspectRatio * Height - Width), pHeight);
			}
			else
			{
				// Inflate the GeographicRectangle to the new height minus the current height
				return new SizeD(pWidth, 
					pHeight + (Width / aspectRatio - Height));
			}
		}

		/// <summary>Returns the horizontal size.</summary>
		public double Width
		{
			get
			{
				return pWidth;
			}
		}

		/// <summary>Returns the vertical size.</summary>
		public double Height
		{
			get
			{
				return pHeight;
			}
		}

		/// <summary>Returns a copy of the current instance.</summary>
		public SizeD Clone()
		{
			return new SizeD(pWidth, pHeight);
		}

		/// <summary>Indicates if the instance has any value.</summary>
		public bool IsEmpty
		{
			get
			{
				return(pWidth == 0 && pHeight == 0);
			}
		}

		/// <summary>Returns the sum of the current instance with the specified size.</summary>
		public SizeD Add(SizeD size)
		{
			return new SizeD(pWidth + size.Width, pHeight + size.Height);
		}

		/// <summary>Returns the current instance decreased by the specified value.</summary>
		public SizeD Subtract(SizeD size)
		{
			return new SizeD(pWidth - size.Width, pHeight - size.Height);
		}
	
		/// <summary>Returns the product of the current instance with the specified value.</summary>
		public SizeD Multiply(SizeD size)
		{
			return new SizeD(pWidth * size.Width, pHeight * size.Height);
		}
	
		/// <summary>Returns the current instance divided by the specified value.</summary>
		public SizeD Divide(SizeD size)
		{
			return new SizeD(pWidth / size.Width, pHeight / size.Height);
		}

        /// <summary>
        /// Compares the current instance to the specified object.
        /// </summary>
        /// <param name="obj">An <strong>Object</strong> to compare with.</param>
        /// <returns>A <strong>Boolean</strong>, True if the values are equivalent.</returns>
        public override bool Equals(object obj)
        {
            if (obj is SizeD)
                // If the type is the same, compare the values
                return Equals((SizeD)obj);
            else
                // Defer to the Object class
                return base.Equals(obj);
        }

        /// <summary>
        /// Compares the current instance to the specified object.
        /// </summary>
        /// <param name="obj">A <strong>SizeD</strong> object to compare with.</param>
        /// <returns>A <strong>Boolean</strong>, True if the values are equivalent.</returns>
        public bool Equals(SizeD obj)
        {
            return pWidth.Equals(obj.Width) && pHeight.Equals(obj.Height);
        }

        public override int GetHashCode()
        {
            return Convert.ToInt32(pWidth) ^ Convert.ToInt32(pHeight);
        }

		public static SizeD Parse(string value)
		{
			return new SizeD(value, CultureInfo.CurrentCulture);
		}

		public static SizeD Parse(string value, CultureInfo culture)
		{
            return new SizeD(value, culture);
		}

#if !PocketPC
		public static explicit operator System.Drawing.SizeF(SizeD value)
		{
			return new System.Drawing.SizeF((float)value.Width, (float)value.Height);
		}
#endif

		[CLSCompliant(false)]
		public static explicit operator System.Drawing.Size(SizeD value)
		{
			return new System.Drawing.Size((int)Math.Round(value.Width, 0), (int)Math.Round(value.Height, 0));
		}

		[CLSCompliant(false)]
		public static explicit operator SizeD(System.Drawing.Size value)
		{
			return new SizeD(value.Width, value.Height);
		}

		[CLSCompliant(false)]
		public static explicit operator SizeD(System.Drawing.SizeF value)
		{
			return new SizeD(value.Width, value.Height);
		}

        public static bool operator ==(SizeD left, SizeD right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(SizeD left, SizeD right)
        {
            return !(left.Equals(right));
        }

		#region IFormattable Members

		public override string ToString()
		{
			return ToString("G", CultureInfo.CurrentCulture);
		}

		public string ToString(string format)
		{
			return ToString(format, CultureInfo.CurrentCulture);
		}

		public string ToString(string format, IFormatProvider formatProvider)
		{
            CultureInfo culture = formatProvider as CultureInfo;
            if (formatProvider == null)
                culture = CultureInfo.CurrentCulture;
            return Width.ToString(format, culture)
                + culture.TextInfo.ListSeparator + " "
                + Height.ToString(format, culture);
		}
	
		string IFormattable.ToString(string format, IFormatProvider formatProvider)
		{
			return this.ToString();
		}

		#endregion

#if !PocketPC || (PocketPC && Framework20)
		#region IXmlSerializable Members

#if Framework20
        public static XmlQualifiedName GetXmlSchema(XmlSchemaSet xs)
        {
            // Load it from a string
            xs.Add(Xml.SchemaSet);

            // And return the type
            return new XmlQualifiedName("SizeD", Xml.Namespace);
        }
#endif

        XmlSchema IXmlSerializable.GetSchema()
        {
            return null;
        }

        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            ReadXml(reader);
        }

        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            WriteXml(writer);
        }

        /// <summary>
        /// Populates the current instance by reading XML from the specified reader.
        /// </summary>
        /// <param name="reader"></param>
        public void ReadXml(XmlReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            XmlSerializer PositionSerializer = new XmlSerializer(typeof(Position));

            // Now read in the remaining values
            while (!reader.EOF)
            {
                // Keep reading until we reach an element
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        switch (reader.Name)
                        {
                            case "Width":
                                reader.Read();
                                pWidth = Double.Parse(reader.ReadString(), CultureInfo.InvariantCulture);
                                break;
                            case "Height":
                                reader.Read();
                                pHeight = Double.Parse(reader.ReadString(), CultureInfo.InvariantCulture);
                                break;
                            default:
                                // Read the next item
                                reader.Read();
                                break;
                        }
                        break;
                    case XmlNodeType.EndElement:
                        // Is this the </SEGMENT> end tag?
                        if (String.Compare(reader.Name, "SizeD", true, CultureInfo.InvariantCulture) == 0)
                            // Yes.  We're done
                            return;
                        else
                            // No.  Keep reading
                            reader.Read();
                        break;
                    default:
                        // Read the next item
                        reader.Read();
                        break;
                }
            }
        }

        /// <summary>
        /// Writes the values of the current instance to the specified XML writer.
        /// </summary>
        /// <param name="writer"></param>
        public void WriteXml(XmlWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException("writer");

            writer.WriteStartElement("Width");
            writer.WriteString(pWidth.ToString("G15", CultureInfo.InvariantCulture));
            writer.WriteEndElement();
            writer.WriteStartElement("Height");
            writer.WriteString(pHeight.ToString("G15", CultureInfo.InvariantCulture));
            writer.WriteEndElement();
        }

		#endregion
#endif

#if !PocketPC || DesignTime
        #region ISerializable Members

        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]  
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Width", pWidth);
            info.AddValue("Height", pHeight);
        }
        #endregion
#endif
	}
}
